/*
使用方法1（推荐，无需修改本机PowerShell脚本运行限制）:
直接在本文件（forward.cs）所在的目录打开PowerShell并运行如下命令（注意大小写）:
Add-Type -Path ".\UDPForward.cs"; [UDPForward]::Main(@('<本地监听端口>:<服务器地址>:<服务器端口>'))
例如:
Add-Type -Path ".\UDPForward.cs"; [UDPForward]::Main(@('11451:myl4d2.com:27014'))
其中myl4d2.com:27014是远程服务器的地址与端口，11451是本地端口
在求生之路游戏控制台中使用connect <本机IP>:11451进行连接即可。本机IP可通过ipconfig命令查看。
注意不要使用127.0.0.1:11451和localhost:11451，求生之路似乎并不支持这两个连接地址。

使用方法2（需要修改本机PowerShell脚本运行限制）：
将上面的命令行存为一个.ps1文件，这样可以记住设置，方便使用。
修改本机PowerShell脚本运行限制可通过管理员模式的PowerShell实现。右键开始菜单按钮，选择Windows PowerShell(管理员)，然后执行下列命令即可：
Set-ExecutionPolicy -Scope CurrentUser Unrestricted

使用方法3（需要修改本机PowerShell脚本运行限制）：
将本文件后缀名改成.ps1，删除本段注释，并在开头添加一行
Add-Type -TypeDefinition @"
再在结尾添加一行
"@; [UDPForward]::Main(@('<本地监听端口>:<服务器地址>:<服务器端口>'))
或者
"@; [UDPForward]::Main($args)
即可。前者是硬编码固定参数，后者是在调用ps1脚本时指定参数。
*/

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

public static class UDPForward
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Source: https://gitee.com/Rikka0w0/l4d2_ipv6");
        Console.WriteLine("UDP forwarder, enables IPv4 UDP to IPv6 UDP forwarding.");
        Console.WriteLine("UDP转发器, 可实现纯IPv4 UDP客户端连接IPv6服务器.");

        // Default settings 默认设置
        int localPort = 27014;
        string remoteHost = "localhost";
        int remotePort = 27015;

        // Parse from args
        if (args.Length > 0) {
				    string[] parts = args[0].Split(':');
				    if (parts.Length == 3) {
				        localPort = Int32.Parse(parts[0]);
				        remoteHost = parts[1];
				        remotePort = Int32.Parse(parts[2]);
				    } else {
				        Console.WriteLine("Invalid argument. The expected format <LocalPort>:<RemoteHost>:<RemotePort>.");
				        Console.WriteLine("参数无效. 正确格式为 <本地监听端口>:<服务器地址>:<服务器端口>.");
				        return;
				    }
        }

        // Define the local IPv4 listening endpoint
        IPAddress localIPAddress = IPAddress.Any;
        IPEndPoint localEndpoint = new IPEndPoint(localIPAddress, localPort);

        // Resolve the remote domain name to an IPv6 address
        IPAddress[] remoteIPAddresses = Dns.GetHostAddresses(remoteHost);
        IPEndPoint remoteEndpoint = new IPEndPoint(remoteIPAddresses[0], remotePort);

        // Create a UDP client socket to listen for incoming IPv4 traffic
        UdpClient udpClient = new UdpClient(localEndpoint);

        // Create a UDP client socket with IPv6 address family to send data to the IPv6 host
        UdpClient udpClientRemote = new UdpClient(remotePort, AddressFamily.InterNetworkV6);
        udpClientRemote.Connect(remoteEndpoint);

        // Start listening for incoming IPv4 traffic
        Console.WriteLine("Listening for incoming UDP IPv4 traffic on local port " + localEndpoint.Port + " and forward it to:" + remoteHost + ":" + remotePort);
        Console.WriteLine("开启UDP转发：从本地" + localPort + "到远程" + remoteHost + ":" + remotePort);

        // Start a thread for receiving IPv4 traffic and forwarding it
        Thread receiveAndForwardThread = new Thread(() =>
        {
            while (true)
            {
                // Receive data from the IPv4 client
                byte[] receivedData = udpClient.Receive(ref localEndpoint);

                // Send the received data to the resolved IPv6 address
                udpClientRemote.Send(receivedData, receivedData.Length);
            }
        });

        // Start a thread for receiving IPv6 traffic
        Thread receiveThreadRemote = new Thread(() =>
        {
            while (true)
            {
                // Receive data from the IPv6 server
                byte[] receivedDataRemote = udpClientRemote.Receive(ref remoteEndpoint);

                // Forward the received data to the local IPv4 client
                udpClient.Send(receivedDataRemote, receivedDataRemote.Length, localEndpoint);
            }
        });

        // Start both threads
        receiveAndForwardThread.Start();
        receiveThreadRemote.Start();

        // Wait for threads to finish (in this case, indefinitely)
        receiveAndForwardThread.Join();
        receiveThreadRemote.Join();
    }
}
